<template>
  <div class="pose-feasibility">
    <!-- 主要内容区域 -->
    <div class="main-content">
      <!-- 左侧控制面板 -->
      <div class="left-panel">
        <!-- 选择部件 -->
        <h3 class="section-title">选择部件</h3>
        <div class="control-section">
          <div class="input-group">
            <div class="input-row" style="width: 269px;">
              <label class="input-label">文件名称</label>
              <input style="width: 100px;" type="text" class="input-field" v-model="fileName" placeholder="请输入文件名称"
                readonly>
              <input type="file" ref="fileInput" @change="handleFileSelect" accept=".txt,.csv,.json"
                style="display: none; width: 100px;">
              <button class="import-btn" :disabled="!dataFrames.trim()" @click="importFile">导入</button>
            </div>
          </div>
        </div>

        <!-- 目标点数 -->
        <h3 class="section-title">目标点数</h3>
        <div class="control-section">
          <div class="input-group">
            <div class="input-row">
              <label class="input-label">目标点数</label>
              <input type="text" class="input-field" v-model="targetPoints" placeholder="请输入目标点数">
            </div>
          </div>
        </div>

        <!-- 相关算法运行所需参数 -->
        <h3 class="section-title">相关算法运行所需参数</h3>
        <div class="control-section">
          <div class="input-group">
            <div class="input-row">
              <label class="input-label">单次保留点数</label>
              <input type="text" class="input-field" v-model="retainPoints" placeholder="请输入单次保留点数">
            </div>
            <div class="input-row">
              <label class="input-label">采用数据帧数</label>
              <input type="text" class="input-field" v-model="dataFrames" placeholder="请输入采用数据帧数">
            </div>
          </div>
        </div>

      </div>

      <!-- 右侧显示区域 -->
      <div class="right-panel">

        <!-- 图像显示区域 -->
        <div class="graph-section">
          <!-- 进度条 -->
          <div class="progress-section">
            <div class="progress-bar">
              <div class="progress-fill" :style="{ width: calculationProgress + '%' }"></div>
              <div class="progress-text">{{ calculationProgress }}%</div>
            </div>
          </div>
        </div>
        <div class="section-header">
          <h3 class="section-title">3D 可视化</h3>
          <div class="section-actions">
            <button class="action-btn" @click="resetView">
              <span class="btn-icon">🔄</span>
              重置视图
            </button>

          </div>
        </div>
        <!-- 视图切换按钮 -->
        <div class="view-switch-container">
          <div class="view-switch-buttons">
            <button class="view-switch-btn" :class="{ active: selectedView === 'front' }" @click="switchView('front')">
              正视图
            </button>
            <button class="view-switch-btn" :class="{ active: selectedView === 'left' }" @click="switchView('left')">
              侧视图
            </button>
            <button class="view-switch-btn" :class="{ active: selectedView === 'over' }" @click="switchView('over')">
              俯视图
            </button>
          </div>
        </div>

        <div class="graph-container">
          <!-- 双图表布局 -->
          <div class="charts-layout">
            <!-- 左侧：2D散点图 -->
            <div class="chart-section">
              <div ref="chart2DRef" class="chart-2d"></div>
            </div>

            <!-- 右侧：3D概率密度图 -->
            <div class="chart-section">
              <div ref="chart3DRef" class="chart-3d"></div>
            </div>
          </div>
        </div>



      </div>
    </div>
    <!-- 日志区域 -->
    <div class="log-section">
      <div class="section-header log-section-header">
        <h3 class="section-title">实时日志</h3>
        <div class="log-controls">
          <button class="control-btn">清空</button>
          <button class="control-btn">导出</button>
        </div>
      </div>
      <div class="log-container">
        <div class="log-content">
          <div class="log-entry" v-for="(entry, index) in logEntries" :key="index" :class="entry.type">
            <span class="log-time">{{ entry.time }}</span>
            <span class="log-message">{{ entry.message }}</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import 'echarts-gl'
import { inflate } from 'pako'
import { importFile as importFileAPI } from '@/api/feasibility'
import {
  beforeSpots as beforeSpotsAPI,
  startCalculation as startCalculationAPI,
  visualizeCalculation as visualizeCalculationAPI,
  parseSpots as parseSpotsAPI,
  parse3DProbabilityDensity as parse3DProbabilityDensityAPI
} from '@/api/placement'
import toast from '@/utils/toast'
import { useCalculationStore } from '@/stores/calculation'
import { getTaskStatus as getTaskStatusAPI } from '@/api/task'
import { getLog as getLogAPI } from '@/api/log'
import { downloadResults as downloadResultsAPI } from '@/api/placement'

const chart2DRef = ref(null)
const chart3DRef = ref(null)
let chart2DInstance = null
let chart3DInstance = null


// 文件上传相关
const fileInput = ref(null)
const fileName = ref('')
const selectedFile = ref(null)

// 数据点存储
const dataPoints = ref([])
const allViewData = ref({
  front_spots: [],
  left_spots: [],
  over_spots: [],
  front_peak_spots: [],
  left_peak_spots: [],
  over_peak_spots: []
})

// 3D概率密度数据
const density3DData = ref(null)

// 当前显示的数据点（包含完整信息）
const currentDataPoints = ref([])

// 当前选择的视图
const selectedView = ref('front')

// 按钮切换视图
const switchView = (view) => {
  selectedView.value = view
  updateDataPointsFromView()
  updateChart()
}

// 可视化计算处理
const handleVisualizeCalculation = async () => {
  try {
    if (!calculationStore.calculationCompleted) {
      toast.error('操作失败', '请先完成计算任务', 3000)
      return
    }

    toast.info('开始可视化计算', '正在调用可视化计算接口...', 2000)

    const params = {
      data: true,
      file_name: fileName.value,
      save: parseInt(retainPoints.value),
      frame: parseInt(dataFrames.value),
      strategy: parseInt(targetPoints.value)
    }


    const response = await visualizeCalculationAPI(params)

    // 检查响应是否包含任务ID
    if (response && response.task_id) {
      // 重置进度条
      calculationProgress.value = 0

      // 更新日志
      logEntries.value.push({
        time: new Date().toLocaleTimeString(),
        message: `可视化计算任务已创建，任务ID: ${response.task_id}`,
        type: 'info'
      })

      // 开始轮询可视化计算任务状态
      console.log('开始轮询可视化计算任务，taskId:', response.task_id, 'taskType: visualize')
      await pollTaskStatus(response.task_id, 'visualize')
    } else {
      // 如果没有任务ID，说明是同步完成
      toast.success('可视化计算成功', '可视化计算已完成', 3000)

      // 更新日志
      logEntries.value.push({
        time: new Date().toLocaleTimeString(),
        message: '可视化计算完成',
        type: 'success'
      })

      console.log('可视化计算响应:', response)
    }

  } catch (error) {
    console.error('可视化计算失败:', error)
    toast.error('可视化计算失败', error.message || '可视化计算过程中出现错误', 5000)

    // 更新日志
    logEntries.value.push({
      time: new Date().toLocaleTimeString(),
      message: `可视化计算失败: ${error.message || '未知错误'}`,
      type: 'error'
    })
  }
}

// 根据选择的视图更新数据点
const updateDataPointsFromView = () => {
  let viewKey, peakKey

  switch (selectedView.value) {
    case 'front':
      viewKey = 'front_spots'
      peakKey = 'front_peak_spots'
      break
    case 'left':
      viewKey = 'left_spots'
      peakKey = 'left_peak_spots'
      break
    case 'over':
      viewKey = 'over_spots'
      peakKey = 'over_peak_spots'
      break
    default:
      viewKey = 'front_spots'
      peakKey = 'front_peak_spots'
  }

  // 合并普通点和峰值点
  const normalPoints = allViewData.value[viewKey] || []
  const peakPoints = allViewData.value[peakKey] || []

  // 数据标准化和验证函数
  const standardizePoint = (point, type) => {
    // 确保 pos 是有效的数组
    if (!point.pos || !Array.isArray(point.pos) || point.pos.length < 2) {
      console.warn('Invalid pos data:', point)
      return null
    }

    // 处理颜色数据 - 兼容两种格式
    let color = [0, 0, 255] // 默认蓝色
    let edgeColor = [0, 0, 255] // 默认边框颜色

    // 优先使用 inner_color（parseSpots格式），其次使用 color（beforeSpots格式）
    let colorSource = point.inner_color || point.color

    if (colorSource && Array.isArray(colorSource)) {
      if (colorSource.length >= 3) {
        // 取前3个值并确保是整数
        color = [
          Math.round(parseFloat(colorSource[0]) || 0),
          Math.round(parseFloat(colorSource[1]) || 0),
          Math.round(parseFloat(colorSource[2]) || 255)
        ]
      } else if (colorSource.length > 0) {
        // 如果长度不足3，用默认值填充
        color = [
          Math.round(parseFloat(colorSource[0]) || 0),
          colorSource.length > 1 ? Math.round(parseFloat(colorSource[1]) || 0) : 0,
          colorSource.length > 2 ? Math.round(parseFloat(colorSource[2]) || 255) : 255
        ]
      }
    }

    // 处理边框颜色
    if (point.edge_color) {
      // parseSpots格式：edge_color 是字符串
      switch (point.edge_color.toLowerCase()) {
        case 'r':
        case 'red':
          edgeColor = [255, 0, 0]
          break
        case 'g':
        case 'green':
          edgeColor = [0, 255, 0]
          break
        case 'b':
        case 'blue':
          edgeColor = [0, 0, 255]
          break
        default:
          edgeColor = color
      }
    } else {
      // beforeSpots格式：没有 edge_color，使用内部颜色作为边框颜色
      edgeColor = color
    }

    // 确保 text 存在
    if (!point.text) {
      point.text = ''
    }

    return {
      ...point,
      type: type,
      pos: [
        parseFloat(point.pos[0]) || 0,
        parseFloat(point.pos[1]) || 0
      ], // 确保只有 x, y 坐标且为数字
      color: color, // 内部颜色
      edgeColor: edgeColor, // 边框颜色
      symbol: type === 'peak' ? (point.symbol || 'x') : 'circle' // 符号类型
    }
  }

  // 为普通点添加类型标记并标准化
  const normalPointsWithType = normalPoints
    .map(point => standardizePoint(point, 'normal'))
    .filter(point => point !== null)

  // 为峰值点添加类型标记并标准化
  const peakPointsWithType = peakPoints
    .map(point => standardizePoint(point, 'peak'))
    .filter(point => point !== null)

  // 合并所有点
  currentDataPoints.value = [...normalPointsWithType, ...peakPointsWithType]

  // 为了兼容性，也保存位置数据
  dataPoints.value = currentDataPoints.value.map(item => item.pos)

  console.log('updateDataPointsFromView 完成:')
  console.log('- 普通点数量:', normalPointsWithType.length)
  console.log('- 峰值点数量:', peakPointsWithType.length)
  console.log('- 总数据点数量:', currentDataPoints.value.length)
  console.log('- 当前视图:', selectedView.value)
  console.log('- 视图键:', viewKey, peakKey)

}

// 使用Pinia store
const calculationStore = useCalculationStore()

// 计算进度
const calculationProgress = ref(0)

// 输入参数
const targetPoints = ref('')
const retainPoints = ref('')
const dataFrames = ref('')

// 日志条目
const logEntries = ref([])

// 定时器引用，用于清理
let pollingTimer = null

// 初始化所有状态
const initializeStates = () => {
  // 重置计算状态
  calculationStore.resetCalculation()

  // 重置进度
  calculationProgress.value = 0

  // 清空数据点
  dataPoints.value = []
  currentDataPoints.value = []
  allViewData.value = {
    front_spots: [],
    left_spots: [],
    over_spots: [],
    front_peak_spots: [],
    left_peak_spots: [],
    over_peak_spots: []
  }

  // 重置视图选择
  selectedView.value = 'front'

  // 清空日志
  logEntries.value = []

  // 重置输入参数
  targetPoints.value = ''
  retainPoints.value = ''
  dataFrames.value = ''

  // 重置文件相关
  fileName.value = ''
  selectedFile.value = null
  if (fileInput.value) {
    fileInput.value.value = ''
  }

  // 更新图表
  updateChart()
}

// 重置视图和数据
const resetView = () => {
  // 显示确认提示
  toast.info('重置视图', '正在重置所有数据和视图...', 2000)

  // 调用状态初始化函数
  initializeStates()

  // 重置图表视图（如果有的话）
  if (chartInstance) {
    chartInstance.resize()
    // 可以添加更多图表重置逻辑
  }

  // 显示重置完成提示
  toast.success('重置完成', '视图和数据已重置，可以重新开始', 3000)
}

// 初始化双图表
const initCharts = () => {
  init2DChart()
  init3DChart()
}

// 初始化2D散点图
const init2DChart = () => {
  if (!chart2DRef.value) return

  chart2DInstance = echarts.init(chart2DRef.value, null, {
    renderer: 'canvas',
    useDirtyRect: true,
    useCoarsePointer: true,
    pointerSize: 2
  })

  update2DChart()
}

// 初始化3D概率密度图
const init3DChart = () => {
  if (!chart3DRef.value) return

  chart3DInstance = echarts.init(chart3DRef.value, null, {
    renderer: 'canvas',
    useDirtyRect: true,
    useCoarsePointer: true,
    pointerSize: 2
  })

  // 不在这里调用update3DChart()，因为此时还没有数据
  // update3DChart() 会在数据获取后调用
}

// 更新2D图表
const update2DChart = () => {
  if (!chart2DInstance) return

  // 使用真实数据点（包含颜色信息）
  const points = currentDataPoints.value || []
  console.log('update2DChart 被调用，数据点数量:', points.length)
  console.log('数据点内容:', points)

  // 如果没有数据，显示空图表框架
  if (points.length === 0) {
    const option = {
      title: {
        show: false
      },
      grid: {
        left: '10%',
        right: '10%',
        top: '10%',
        bottom: '6%',
        containLabel: true
      },
      xAxis: {
        type: 'value',
        name: 'X轴',
        nameLocation: 'middle',
        nameGap: 30,
        min: -500,
        max: 1000,
        axisPointer: {
          show: true,
          lineStyle: {
            color: '#e2e8f0', // 浅色标签
            type: 'dashed'
          }
        },
        axisLine: {
          lineStyle: { color: '#666' },
          onZero: false
        },
        axisTick: {
          show: true,
          alignWithLabel: true
        },
        axisLabel: {
          color: '#e2e8f0', // 浅色标签
          formatter: function (value) {
            if (value === 0) return '0'
            if (value === 1000) return '1000'
            return ''
          }
        },
        splitLine: { show: false }
      },
      yAxis: {
        type: 'value',
        name: 'Y轴',
        nameLocation: 'middle',
        nameGap: 50,
        min: -500,
        max: 1000,
        axisPointer: {
          show: true,
          lineStyle: {
            color: '#e2e8f0', // 浅色标签
            type: 'dashed'
          }
        },
        axisLine: {
          lineStyle: { color: '#666' },
          onZero: false
        },
        axisTick: {
          show: true,
          alignWithLabel: true
        },
        axisLabel: {
          color: '#e2e8f0', // 浅色标签
          formatter: function (value) {
            if (value === 0) return '0'
            if (value === 1000) return '1000'
            return ''
          }
        },
        splitLine: { show: false }
      },
      series: []
    }
    chart2DInstance.setOption(option, true) // 强制更新
    return
  }

  // 使用固定的坐标轴范围

  const option = {
    title: {
      show: false
    },
    grid: {
      left: '10%',
      right: '10%',
      top: '10%',
      bottom: '15%',
      containLabel: true
    },
    xAxis: {
      type: 'value',
      name: 'X轴',
      nameLocation: 'middle',
      nameGap: 30,
      min: -500,
      axisLine: {
        lineStyle: { color: '#666' },
        onZero: false
      },
      axisTick: {
        show: true,
        alignWithLabel: true
      },
      axisTick: { show: true },
      axisLabel: {
        color: '#e2e8f0', // 浅色标签
        formatter: function (value) {
          if (value === 0) return '0'
          if (value === 1000) return '1000'
          return ''
        }
      },
      splitLine: {
        show: false
      }
    },
    yAxis: {
      type: 'value',
      name: 'Y轴',
      nameLocation: 'middle',
      nameGap: 50,
      min: -500,
      axisLine: {
        lineStyle: { color: '#666' },
        onZero: false
      },
      axisTick: {
        show: true,
        alignWithLabel: true
      },
      axisTick: { show: true },
      axisLabel: {
        color: '#e2e8f0', // 浅色标签
        formatter: function (value) {
          if (value === 0) return '0'
          if (value === 1000) return '1000'
          return ''
        }
      },
      splitLine: {
        show: false
      }
    },
    series: [
      // 普通点（实心圆）
      {
        name: '普通点',
        type: 'scatter',
        data: points.filter(point => point.type === 'normal').map(point => ({
          value: point.pos,
          name: point.text,
          itemStyle: {
            color: `rgb(${point.color[0]}, ${point.color[1]}, ${point.color[2]})`,
            borderColor: `rgb(${point.edgeColor[0]}, ${point.edgeColor[1]}, ${point.edgeColor[2]})`,
            borderWidth: 1
          }
        })),
        symbol: 'circle',
        symbolSize: 4,
        label: {
          show: true,
          position: 'top',
          fontSize: 8,
          color: '#fff',
          fontWeight: 'bold',
          formatter: function (params) {
            // 只显示部分标签，减少密集度
            const index = parseInt(params.data.name)
            if (index % 3 === 0 || index <= 20) {
              return params.data.name
            }
            return ''
          }
        },
        emphasis: {
          itemStyle: {
            shadowBlur: 15,
            shadowColor: 'rgba(0, 0, 0, 0.3)'
          }
        }
      },
      // 峰值点（X标记）
      {
        name: '峰值点',
        type: 'scatter',
        data: points.filter(point => point.type === 'peak').map(point => ({
          value: point.pos,
          name: point.text,
          itemStyle: {
            color: `rgb(${point.color[0]}, ${point.color[1]}, ${point.color[2]})`,
            borderColor: `rgb(${point.edgeColor[0]}, ${point.edgeColor[1]}, ${point.edgeColor[2]})`,
            borderWidth: 1
          }
        })),
        symbol: 'path://M0,0 L20,20 M20,0 L0,20',
        symbolSize: 4,
        label: {
          show: true,
          position: 'top',
          fontSize: 9,
          color: '#ff6b6b',
          fontWeight: 'bold',
          formatter: function (params) {
            // 显示所有峰值点的text标签，用不同颜色区分
            return params.data.name
          }
        },
        emphasis: {
          itemStyle: {
            shadowBlur: 15,
            shadowColor: 'rgba(0, 0, 0, 0.3)'
          }
        }
      }
    ],
    tooltip: {
      trigger: 'item',
      formatter: function (params) {
        const pos = params.data.value
        return `坐标: (${pos[0].toFixed(2)}, ${pos[1].toFixed(2)})`
      }
    },
    animation: true,
    animationDuration: 1000
  }

  chart2DInstance.setOption(option, true) // 强制更新
}

// 更新3D图表
const update3DChart = () => {
  if (!chart3DInstance) {
    console.warn('chart3DInstance 未初始化')
    return
  }

  if (!chart3DRef.value) {
    console.warn('chart3DRef 未找到')
    return
  }

  console.log('update3DChart 被调用')
  console.log('density3DData.value:', density3DData.value)

  // 使用真实数据或返回空数据
  const generate3DData = () => {
    // 如果有真实的3D概率密度数据，使用它
    if (density3DData.value) {
      try {
        console.log('3D数据原始响应:', density3DData.value)

        // 验证数据是否为有效的base64字符串
        const isValidBase64 = (str) => {
          try {
            return btoa(atob(str)) === str
          } catch (err) {
            return false
          }
        }

        // 检查API响应格式
        let gridData = null
        let shape = [100, 100] // 默认形状

        // 如果API返回的是压缩的3D网格数据
        if (typeof density3DData.value === 'string') {
          // 直接是base64编码的压缩数据
          if (!isValidBase64(density3DData.value)) {
            console.error('API返回的数据不是有效的base64格式')
            return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
          }

          try {
            // 尝试解压缩数据
            const decompressedData = JSON.parse(atob(density3DData.value))
            console.log('解压缩后的数据:', decompressedData)

            if (decompressedData.data && Array.isArray(decompressedData.data)) {
              gridData = decompressedData.data
              shape = decompressedData.shape || [100, 100]
            } else if (Array.isArray(decompressedData)) {
              gridData = decompressedData
            } else {
              console.error('解压缩后的数据格式不正确:', decompressedData)
              return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
            }
          } catch (parseError) {
            console.error('解析压缩数据失败:', parseError)
            return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
          }
        }
        // 如果API返回的是对象格式
        else if (typeof density3DData.value === 'object') {
          // 检查是否有单独的X, Y, Z字段（旧格式）
          if (density3DData.value.X && density3DData.value.Y && density3DData.value.Z) {
            const xDataStr = density3DData.value.X
            const yDataStr = density3DData.value.Y
            const zDataStr = density3DData.value.Z

            console.log('检测到旧格式数据，尝试解析X, Y, Z字段')

            if (!isValidBase64(xDataStr) || !isValidBase64(yDataStr) || !isValidBase64(zDataStr)) {
              console.error('X, Y, Z数据不是有效的base64格式')
              return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
            }

            try {
              // 先尝试直接解析base64数据
              console.log('尝试解析X数据，长度:', xDataStr.length)
              console.log('X数据前100个字符:', xDataStr.substring(0, 100))

              // 检查是否是压缩数据（通常以特定字节开头）
              const xDecoded = atob(xDataStr)
              const yDecoded = atob(yDataStr)
              const zDecoded = atob(zDataStr)

              console.log('解码后的X数据前50个字节:', Array.from(xDecoded.substring(0, 50)).map(b => b.charCodeAt(0)))
              console.log('解码后的X数据前50个字符:', xDecoded.substring(0, 50))

              // 检查是否是压缩数据
              const xBytes = new Uint8Array(xDecoded.split('').map(c => c.charCodeAt(0)))

              // 检查gzip格式 (magic number: 1f 8b)
              const isGzip = xBytes.length >= 2 && xBytes[0] === 0x1f && xBytes[1] === 0x8b

              // 检查zlib格式 (magic number: 78 9c 或 78 da)
              const isZlib = xBytes.length >= 2 && xBytes[0] === 0x78 && (xBytes[1] === 0x9c || xBytes[1] === 0xda)

              console.log('是否为gzip压缩数据:', isGzip)
              console.log('是否为zlib压缩数据:', isZlib)

              let xData, yData, zData

              if (isGzip || isZlib) {
                console.log('检测到压缩数据，使用pako解压缩')
                try {
                  // 将字符串转换为Uint8Array
                  const xBytes = new Uint8Array(xDecoded.split('').map(c => c.charCodeAt(0)))
                  const yBytes = new Uint8Array(yDecoded.split('').map(c => c.charCodeAt(0)))
                  const zBytes = new Uint8Array(zDecoded.split('').map(c => c.charCodeAt(0)))

                  // 使用pako解压缩 (inflate可以处理gzip和zlib格式)
                  // 解压缩为Uint8Array而不是字符串
                  const xDecompressed = inflate(xBytes)
                  const yDecompressed = inflate(yBytes)
                  const zDecompressed = inflate(zBytes)

                  console.log('解压缩后的X数据前50个字节:', xDecompressed.slice(0, 50))

                  // 将解压缩后的字节数据转换为Float32Array
                  xData = new Float32Array(xDecompressed.buffer)
                  yData = new Float32Array(yDecompressed.buffer)
                  zData = new Float32Array(zDecompressed.buffer)

                  console.log('解压缩成功，数据长度:', xData.length, yData.length, zData.length)
                  console.log('X数据前10个值:', Array.from(xData.slice(0, 10)))
                  console.log('Y数据前10个值:', Array.from(yData.slice(0, 10)))
                  console.log('Z数据前10个值:', Array.from(zData.slice(0, 10)))
                } catch (decompressError) {
                  console.error('解压缩失败:', decompressError)
                  return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
                }
              } else {
                // 尝试直接解析为JSON
                xData = JSON.parse(xDecoded)
                yData = JSON.parse(yDecoded)
                zData = JSON.parse(zDecoded)
              }

              // 检查数据是否为数组或类数组对象（如Float32Array）
              const isArrayLike = (data) => Array.isArray(data) || (data && typeof data.length === 'number' && data.length > 0)

              if (!isArrayLike(xData) || !isArrayLike(yData) || !isArrayLike(zData)) {
                console.error('解码后的X, Y, Z数据不是数组格式')
                console.log('xData类型:', typeof xData, '是否为数组:', Array.isArray(xData), '长度:', xData?.length)
                console.log('yData类型:', typeof yData, '是否为数组:', Array.isArray(yData), '长度:', yData?.length)
                console.log('zData类型:', typeof zData, '是否为数组:', Array.isArray(zData), '长度:', zData?.length)
                return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
              }

              // 获取网格形状
              shape = density3DData.value.metadata?.shape || [100, 100]
              const expectedLength = shape[0] * shape[1]

              console.log('网格形状:', shape)
              console.log('预期数据长度:', expectedLength)
              console.log('实际数据长度 - X:', xData.length, 'Y:', yData.length, 'Z:', zData.length)

              if (xData.length !== expectedLength || yData.length !== expectedLength || zData.length !== expectedLength) {
                console.error('数据长度不匹配预期形状')
                console.log('实际长度 - X:', xData.length, 'Y:', yData.length, 'Z:', zData.length)
                console.log('预期长度:', expectedLength, '形状:', shape)
                return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
              }

              // 验证数据有效性
              let validPoints = 0
              let invalidPoints = 0

              for (let i = 0; i < xData.length; i++) {
                const x = xData[i]
                const y = yData[i]
                const z = zData[i]

                if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number' &&
                  !isNaN(x) && !isNaN(y) && !isNaN(z)) {
                  validPoints++
                } else {
                  invalidPoints++
                  if (invalidPoints <= 5) { // 只打印前5个无效点的信息
                    console.log(`无效点 ${i}: x=${x}, y=${y}, z=${z}`)
                  }
                }
              }

              console.log(`数据点统计 - 有效点: ${validPoints}, 无效点: ${invalidPoints}`)

              // 返回网格数据而不是点云数据
              console.log('成功解析网格数据')
              return { data: [], shape, xData: Array.from(xData), yData: Array.from(yData), zData: Array.from(zData) }
            } catch (error) {
              console.error('解析X, Y, Z数据失败:', error)
              return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
            }
          }
          // 检查是否有网格数据字段
          else if (density3DData.value.data && Array.isArray(density3DData.value.data)) {
            gridData = density3DData.value.data
            shape = density3DData.value.shape || [100, 100]
          }
          // 检查是否有base64编码的网格数据
          else if (density3DData.value.grid_data) {
            if (!isValidBase64(density3DData.value.grid_data)) {
              console.error('grid_data不是有效的base64格式')
              return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
            }

            try {
              const decompressedData = JSON.parse(atob(density3DData.value.grid_data))
              gridData = decompressedData.data || decompressedData
              shape = density3DData.value.shape || decompressedData.shape || [100, 100]
            } catch (parseError) {
              console.error('解析grid_data失败:', parseError)
              return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
            }
          }
          else {
            console.error('无法识别的数据格式:', density3DData.value)
            return { data: [], shape: [100, 100], xData: [], yData: [], zData: [] }
          }
        }

        // 处理网格数据
        if (gridData && Array.isArray(gridData)) {
          const data = []

          // 如果网格数据是3D数组格式 [[x, y, z], ...]
          if (gridData.length > 0 && Array.isArray(gridData[0]) && gridData[0].length === 3) {
            for (const point of gridData) {
              if (Array.isArray(point) && point.length === 3) {
                const [x, y, z] = point
                if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number' &&
                  !isNaN(x) && !isNaN(y) && !isNaN(z) && z > 0) {
                  data.push([x, y, z])
                }
              }
            }
          }
          // 如果网格数据是2D数组格式，需要根据形状重构
          else if (gridData.length === shape[0] * shape[1]) {
            // 假设数据是扁平化的，需要根据形状重构
            for (let i = 0; i < shape[0]; i++) {
              for (let j = 0; j < shape[1]; j++) {
                const index = i * shape[1] + j
                const value = gridData[index]
                if (typeof value === 'number' && !isNaN(value) && value > 0) {
                  // 这里需要根据实际的数据结构来确定x, y坐标
                  // 假设是均匀网格
                  const x = (j / (shape[1] - 1)) * 2 - 1 // 归一化到[-1, 1]
                  const y = (i / (shape[0] - 1)) * 2 - 1 // 归一化到[-1, 1]
                  data.push([x, y, value])
                }
              }
            }
          }

          console.log('成功生成3D数据点数量:', data.length)
          return { data, shape }
        }

        console.warn('无法解析3D数据，数据格式未知')
        return { data: [], shape: [100, 100] }
      } catch (error) {
        console.error('解析3D概率密度数据失败:', error)
        console.error('错误详情:', {
          message: error.message,
          stack: error.stack,
          density3DData: density3DData.value
        })
        return { data: [], shape: [100, 100] }
      }
    }

    // 如果没有真实数据，返回空数组
    return { data: [], shape: [100, 100] }
  }


  const { data, shape, xData, yData, zData } = generate3DData()

  // 检查是否有网格数据
  let sampledData = []
  if (xData && yData && zData && xData.length > 0) {
    console.log('使用网格数据生成3D表面')
    // 使用网格数据，不需要采样
  } else {
    console.log('使用点云数据，进行采样')
    // 对数据进行采样以提高3D渲染性能
    sampledData = data
    if (data.length > 2000) {
      const sampleRate = Math.ceil(data.length / 2000) // 采样到约2000个点
      sampledData = data.filter((_, index) => index % sampleRate === 0)
      console.log(`数据采样: 从 ${data.length} 个点采样到 ${sampledData.length} 个点`)
    }
  }

  // 生成surface数据格式的函数
  function generateSurfaceData(xDataOrPoints, yData, zData, shape) {
    console.log('generateSurfaceData被调用，参数数量:', arguments.length)
    console.log('参数类型 - xDataOrPoints:', typeof xDataOrPoints, 'yData:', typeof yData, 'zData:', typeof zData, 'shape:', typeof shape)

    // 如果只有两个参数，说明是点云数据
    if (arguments.length === 2) {
      const points = xDataOrPoints
      const shape = yData

      if (!points || points.length === 0 || !shape) {
        console.warn('缺少必要的点云数据')
        return []
      }

      const [rows, cols] = shape
      const surfaceData = []

      // 创建网格数据 - 对于surface，我们需要创建规则的网格
      for (let i = 0; i < rows; i++) {
        const row = []
        for (let j = 0; j < cols; j++) {
          const index = i * cols + j
          if (index < points.length) {
            const point = points[index]
            // 对于surface，我们需要确保x, y坐标是规则的网格
            const x = -1000 + (j / (cols - 1)) * 2000 // X轴从-1000到1000
            const y = -500 + (i / (rows - 1)) * 1500  // Y轴从-500到1000
            const z = point[2] // 使用原始的Z值（密度值）
            row.push([x, y, z])
          } else {
            // 如果没有对应的点，使用插值或默认值
            const x = -1000 + (j / (cols - 1)) * 2000
            const y = -500 + (i / (rows - 1)) * 1500
            row.push([x, y, 0])
          }
        }
        surfaceData.push(row)
      }

      console.log('生成的surface数据形状（点云）:', surfaceData.length, 'x', surfaceData[0]?.length)
      return surfaceData
    }

    // 如果有四个参数，说明是网格数据
    if (!xDataOrPoints || !yData || !zData || !shape) {
      console.warn('缺少必要的网格数据')
      return []
    }

    const [rows, cols] = shape
    const surfaceData = []

    console.log('生成surface数据 - 形状:', shape, 'X数据长度:', xDataOrPoints.length, 'Y数据长度:', yData.length, 'Z数据长度:', zData.length)

    // 对于ECharts scatter3D，我们需要生成点云数据
    // 每个点包含[x, y, z]坐标
    let validPoints = 0
    let invalidPoints = 0

    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < cols; j++) {
        const index = i * cols + j
        if (index < xDataOrPoints.length && index < yData.length && index < zData.length) {
          // 使用实际的X、Y、Z坐标值
          const x = xDataOrPoints[index]
          const y = yData[index]
          const z = zData[index]

          // 确保坐标值是有效的数字
          if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number' &&
            !isNaN(x) && !isNaN(y) && !isNaN(z) && z >= 0) {
            // 放大Z值以增强3D效果 (乘以1e7)
            const scaledZ = z * 1e7
            surfaceData.push([x, y, scaledZ])
            validPoints++
          } else {
            invalidPoints++
            if (invalidPoints <= 5) {
              console.log(`无效点 ${index}: x=${x}, y=${y}, z=${z}`)
            }
          }
        }
      }
    }

    console.log(`数据点统计 - 有效点: ${validPoints}, 无效点: ${invalidPoints}`)

    console.log('生成的scatter3D数据点数量:', surfaceData.length)
    console.log('scatter3D数据前几个点:', surfaceData.slice(0, 3))
    console.log('Z值范围:', Math.min(...surfaceData.map(p => p[2])), '到', Math.max(...surfaceData.map(p => p[2])))
    return surfaceData
  }

  // 如果没有数据，显示空状态
  if (data.length === 0 && (!xData || xData.length === 0)) {
    const emptyOption = {
      title: {
        text: '3D Probability Density Estimation',
        left: 'center',
        top: 'center',
        textStyle: {
          fontSize: 16,
          fontWeight: 'bold',
          color: '#999'
        }
      },
      grid3D: {
        boxWidth: 200,
        boxDepth: 200,
        boxHeight: 100,
        axisPointer: {
          show: false
        },
        environment: '#f0f0f0',
        light: {
          main: {
            intensity: 1.2,
            shadow: true
          },
          ambient: {
            intensity: 0.3
          }
        },
        viewControl: {
          distance: 300,
          alpha: 20,
          beta: 40,
          rotateSensitivity: 1,
          zoomSensitivity: 1,
          panSensitivity: 1
        }
      },
      xAxis3D: {
        type: 'value',
        name: 'X Axis',
        min: -1000,
        max: 1000,
        axisLine: { lineStyle: { color: '#e2e8f0' } }, // 浅色坐标轴
        axisLabel: {
          color: '#e2e8f0', // 浅色标签
          formatter: function (value) {
            if (value === -500) return '-500'
            if (value === 0) return '0'
            if (value === 500) return '500'
            if (value === 1000) return '1000'
            return ''
          }
        }
      },
      yAxis3D: {
        type: 'value',
        name: 'Y Axis',
        min: -500,
        max: 1000,
        axisLine: { lineStyle: { color: '#e2e8f0' } }, // 浅色坐标轴
        axisLabel: {
          color: '#e2e8f0', // 浅色标签
          formatter: function (value) {
            if (value === -500) return '-500'
            if (value === 0) return '0'
            if (value === 500) return '500'
            if (value === 1000) return '1000'
            return ''
          }
        }
      },
      zAxis3D: {
        type: 'value',
        name: '1e-Density',
        min: 0,
        max: 8,
        axisLine: { lineStyle: { color: '#e2e8f0' } }, // 浅色坐标轴
        axisLabel: {
          color: '#e2e8f0', // 浅色标签
          formatter: function (value) {
            if (value === 0) return '0'
            if (value === 2) return '2'
            if (value === 4) return '4'
            if (value === 6) return '6'
            if (value === 8) return '8'
            return ''
          }
        }
      },
      series: []
    }

    console.log('显示空状态3D图表')
    chart3DInstance.setOption(emptyOption, true)
    return
  }

  const option = {
    title: {
      text: '3D Probability Density Estimation',
      left: 'center',
      top: 10,
      textStyle: {
        fontSize: 16,
        fontWeight: 'bold'
      }
    },
    grid3D: {
      boxWidth: 200,
      boxDepth: 200,
      boxHeight: 200, // 增加Z轴高度以增强3D效果
      axisPointer: {
        show: false
      },
      // 显示3D网格线
      splitLine: {
        show: true,
        lineStyle: {
          color: '#4a5568', // 深色网格线
          opacity: 0.3
        }
      },
      environment: '#1a202c', // 深色背景
      light: {
        main: {
          intensity: 1.5,
          shadow: true,
          shadowQuality: 'high'
        },
        ambient: {
          intensity: 0.4
        }
      },
      viewControl: {
        distance: 400, // 增加距离以显示更多内容
        alpha: 15, // 调整垂直角度
        beta: 45, // 调整水平角度
        rotateSensitivity: 1,
        zoomSensitivity: 1,
        panSensitivity: 1,
        autoRotate: false,
        autoRotateDirection: 'cw',
        autoRotateSpeed: 10,
        // 添加边界控制
        minDistance: 200,
        maxDistance: 800
      }
    },
    xAxis3D: {
      type: 'value',
      name: 'X Axis',
      nameLocation: 'middle',
      nameGap: 30,
      min: -1000,
      max: 1000,
      axisLine: { lineStyle: { color: '#e2e8f0' } }, // 浅色坐标轴
      splitLine: {
        show: true,
        lineStyle: {
          color: '#4a5568',
          opacity: 0.2
        }
      },
      axisLabel: {
        color: '#e2e8f0', // 浅色标签
        margin: 10, // 增加标签边距
        formatter: function (value) {
          if (value === -500) return '-500'
          if (value === 0) return '0'
          if (value === 500) return '500'
          if (value === 1000) return '1000'
          return ''
        }
      }
    },
    yAxis3D: {
      type: 'value',
      name: 'Y Axis',
      nameLocation: 'middle',
      nameGap: 30,
      min: -500,
      max: 1000,
      axisLine: { lineStyle: { color: '#e2e8f0' } }, // 浅色坐标轴
      splitLine: {
        show: true,
        lineStyle: {
          color: '#4a5568',
          opacity: 0.2
        }
      },
      axisLabel: {
        color: '#e2e8f0', // 浅色标签
        margin: 10, // 增加标签边距
        formatter: function (value) {
          if (value === -500) return '-500'
          if (value === 0) return '0'
          if (value === 500) return '500'
          if (value === 1000) return '1000'
          return ''
        }
      }
    },
    zAxis3D: {
      type: 'value',
      name: 'Density (×1e7)',
      nameLocation: 'middle',
      nameGap: 30,
      min: 0,
      max: 4, // 根据实际数据范围调整
      axisLine: { lineStyle: { color: '#e2e8f0' } }, // 浅色坐标轴
      splitLine: {
        show: true,
        lineStyle: {
          color: '#4a5568',
          opacity: 0.2
        }
      },
      axisLabel: {
        color: '#e2e8f0', // 浅色标签
        margin: 10, // 增加标签边距
        formatter: function (value) {
          if (value === 0) return '0'
          if (value === 1) return '1'
          if (value === 2) return '2'
          if (value === 3) return '3'
          if (value === 4) return '4'
          return ''
        }
      }
    },
    series: [
      {
        type: 'scatter3D',
        data: (() => {
          console.log('Series配置 - xData存在:', !!xData, 'yData存在:', !!yData, 'zData存在:', !!zData, 'sampledData长度:', sampledData.length)
          if (xData && yData && zData) {
            console.log('使用网格数据调用generateSurfaceData')
            const surfaceData = generateSurfaceData(xData, yData, zData, shape)
            console.log('生成的surface数据长度:', surfaceData.length)
            console.log('生成的surface数据前2个点:', surfaceData.slice(0, 2))
            return surfaceData
          } else if (sampledData.length > 0) {
            console.log('使用点云数据调用generateSurfaceData')
            return generateSurfaceData(sampledData, shape)
          } else {
            console.log('返回空数组')
            return []
          }
        })(),
        // scatter3D图表配置
        itemStyle: {
          color: function (params) {
            // 对于scatter3D图表，params.data是[x, y, z]数组
            // z值已经被放大了1e7倍
            const z = params.data[2]
            if (typeof z !== 'number' || isNaN(z)) {
              return '#8000FF' // 默认紫色
            }
            if (z > 3) return '#FFD700' // 黄色 - 高密度
            if (z > 2) return '#FFA500' // 橙色
            if (z > 1) return '#00FF00' // 绿色
            if (z > 0.5) return '#0080FF' // 蓝色
            return '#8000FF' // 紫色 - 低密度
          },
          opacity: 0.8
        },
        emphasis: {
          itemStyle: {
            opacity: 1
          }
        }
      },
      // 添加峰值点
      {
        type: 'scatter3D',
        data: density3DData.value?.peaks || [],
        symbolSize: 8,
        itemStyle: {
          color: '#FF0000', // 红色峰值点
          opacity: 1
        },
        emphasis: {
          itemStyle: {
            opacity: 1
          }
        }
      }
    ],
    // 添加颜色条
    visualMap: {
      min: 0,
      max: 4, // 根据放大后的Z值范围调整
      dimension: 2,
      orient: 'vertical',
      right: 10,
      top: 'center',
      text: ['HIGH', 'LOW'],
      calculable: true,
      inRange: {
        color: ['#8000FF', '#0080FF', '#00FF00', '#FFA500', '#FFD700']
      },
      textStyle: {
        color: '#666'
      }
    },
    tooltip: {
      formatter: function (params) {
        // 对于scatter3D图表，params.data是[x, y, z]数组
        // z值已经被放大了1e7倍，需要还原显示
        const x = params.data[0]
        const y = params.data[1]
        const z = params.data[2] / 1e7 // 还原原始Z值

        return `X: ${x.toFixed(2)}<br/>Y: ${y.toFixed(2)}<br/>Density: ${z.toFixed(8)}`
      }
    }
  }

  console.log('准备更新3D图表')
  if (xData && yData && zData) {
    console.log('使用网格数据，数据点数量:', xData.length)
    console.log('网格数据前3个X值:', xData.slice(0, 3))
  } else {
    console.log('使用采样数据，数据点数量:', sampledData.length)
    console.log('采样后数据前3个点:', sampledData.slice(0, 3))
  }
  chart3DInstance.setOption(option, true) // 强制更新

  // 强制重新渲染
  setTimeout(() => {
    chart3DInstance.resize()

  }, 100)


}

// 组件挂载后初始化图表
onMounted(() => {
  nextTick(() => {
    initCharts()
  })

  // 重置计算状态，确保页面加载时状态正确
  calculationStore.resetCalculation()
})

// 组件卸载时清理定时器
onUnmounted(() => {
  if (pollingTimer) {
    clearTimeout(pollingTimer)
    pollingTimer = null
  }
})
// 处理文件选择
const handleFileSelect = async (event) => {
  const file = event.target.files[0]
  if (!file) return

  selectedFile.value = file
  fileName.value = file.name

  // 选择文件后直接上传
  await uploadFile(file)
}

// 上传文件
const uploadFile = async (file) => {
  try {
    // 创建FormData对象
    const formData = new FormData()
    formData.append('file', file)

    // 第一步：上传文件
    const uploadResponse = await importFileAPI(formData)
    if (uploadResponse && uploadResponse.filename) {
      // 显示上传成功提示
      toast.success('文件上传成功', `文件 ${uploadResponse.filename} 已成功上传`, 2000)

      // 第二步：解析文件
      try {
        // 获取采用数据帧数
        const frame = parseInt(dataFrames.value)

        const parseResponse = await beforeSpotsAPI(uploadResponse.filename, frame)
        console.log(parseResponse, 'parseResponse')
        if (parseResponse && parseResponse.front_spots && Array.isArray(parseResponse.front_spots)) {
          // 显示解析成功提示
          const totalPoints = (parseResponse.front_spots ? parseResponse.front_spots.length : 0) +
            (parseResponse.left_spots ? parseResponse.left_spots.length : 0) +
            (parseResponse.over_spots ? parseResponse.over_spots.length : 0) +
            (parseResponse.front_peak_spots ? parseResponse.front_peak_spots.length : 0) +
            (parseResponse.left_peak_spots ? parseResponse.left_peak_spots.length : 0) +
            (parseResponse.over_peak_spots ? parseResponse.over_peak_spots.length : 0)
          toast.success('文件解析成功', `文件 ${uploadResponse.filename} 解析完成，共 ${totalPoints} 个数据点（帧数：${frame}）`, 3000)

          // 数据标准化函数
          const standardizeDataArray = (dataArray) => {
            if (!Array.isArray(dataArray)) return []

            return dataArray.map(point => {
              // 确保 pos 是有效的数组
              if (!point.pos || !Array.isArray(point.pos) || point.pos.length < 2) {
                console.warn('Invalid pos data:', point)
                return null
              }

              // 强制标准化 color 数组为3维
              let color = [0, 0, 255] // 默认蓝色
              if (point.color && Array.isArray(point.color)) {
                if (point.color.length >= 3) {
                  // 取前3个值并确保是整数
                  color = [
                    Math.round(parseFloat(point.color[0]) || 0),
                    Math.round(parseFloat(point.color[1]) || 0),
                    Math.round(parseFloat(point.color[2]) || 255)
                  ]
                } else if (point.color.length > 0) {
                  // 如果长度不足3，用默认值填充
                  color = [
                    Math.round(parseFloat(point.color[0]) || 0),
                    point.color.length > 1 ? Math.round(parseFloat(point.color[1]) || 0) : 0,
                    point.color.length > 2 ? Math.round(parseFloat(point.color[2]) || 255) : 255
                  ]
                }
              }

              // 确保 text 存在
              if (!point.text) {
                point.text = ''
              }

              return {
                ...point,
                pos: [
                  parseFloat(point.pos[0]) || 0,
                  parseFloat(point.pos[1]) || 0
                ], // 确保只有 x, y 坐标且为数字
                color: color // 确保是标准的3维整数数组
              }
            }).filter(point => point !== null)
          }

          // 存储所有视图的数据（标准化后）
          allViewData.value = {
            front_spots: standardizeDataArray(parseResponse.front_spots),
            left_spots: standardizeDataArray(parseResponse.left_spots),
            over_spots: standardizeDataArray(parseResponse.over_spots),
            front_peak_spots: standardizeDataArray(parseResponse.front_peak_spots),
            left_peak_spots: standardizeDataArray(parseResponse.left_peak_spots),
            over_peak_spots: standardizeDataArray(parseResponse.over_peak_spots)
          }

          // 根据当前选择的视图更新数据点
          updateDataPointsFromView()
          updateChart()
        } else {
          throw new Error('解析响应数据格式错误')
        }
      } catch (parseError) {
        toast.error('文件解析失败', parseError.message || '文件解析出错', 5000)

      }
    } else {
      throw new Error('上传响应格式错误')
    }
  } catch (error) {
    // 显示错误提示
    toast.error('文件上传失败', error.message || '请检查文件格式或网络连接', 5000)

  }
}

// 导入文件（点击按钮触发文件选择）
const importFile = () => {
  // 检查数据帧数是否已输入
  if (!dataFrames.value.trim()) {
    toast.error('导入失败', '请先输入采用数据帧数', 3000)
    return
  }

  fileInput.value.click()
}

// 更新图表
const updateChart = () => {
  update2DChart()
  update3DChart()
}
// 验证输入数据
const validateInputs = () => {
  const errors = []

  // 检查文件是否导入成功
  if (!selectedFile.value || !dataPoints.value || dataPoints.value.length === 0) {
    errors.push('请先导入数据文件')
  }

  // 检查目标点数
  if (!targetPoints.value.trim()) {
    errors.push('请输入目标点数')
  }

  // 检查单次保留点数
  if (!retainPoints.value.trim()) {
    errors.push('请输入单次保留点数')
  }

  // 检查采用数据帧数
  if (!dataFrames.value.trim()) {
    errors.push('请输入采用数据帧数')
  }

  return errors
}

// 重试函数
const retryAPI = async (apiCall, maxRetries = 3, delay = 2000) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await apiCall()
    } catch (error) {
      console.log(`API调用失败，第 ${i + 1} 次重试...`, error.message)

      if (i === maxRetries - 1) {
        throw error // 最后一次重试失败，抛出错误
      }

      // 等待后重试
      await new Promise(resolve => setTimeout(resolve, delay * (i + 1)))
    }
  }
}

// 轮询任务状态
const pollTaskStatus = async (taskId, taskType = 'placement') => {
  console.log('pollTaskStatus 开始，taskId:', taskId, 'taskType:', taskType)
  // 根据任务类型设置不同的超时时间
  const maxAttempts = taskType === 'placement' ? 300 : 60 // 普通计算10分钟，可视化计算2分钟
  let attempts = 0
  let pollInterval = 1000 // 初始1秒
  let consecutiveNoProgress = 0 // 连续无进度次数

  const poll = async () => {
    try {
      attempts++
      const res = await getTaskStatusAPI(taskId)

      // 获取任务日志
      try {
        const logResponse = await getLogAPI(taskId)
        if (logResponse && logResponse.logs) {
          // 解析服务器返回的日志字符串数组
          logResponse.logs.forEach(logString => {
            // 解析日志格式: "2025-10-15 21:55:49,797 - INFO - message"
            const logMatch = logString.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) - (\w+) - (.+)$/)
            if (logMatch) {
              const [, timestamp, level, message] = logMatch
              // 转换时间格式
              const timeStr = timestamp.replace(',', '.').replace(' ', 'T') + 'Z'
              const logTime = new Date(timeStr).toLocaleTimeString()

              // 映射日志级别
              let logType = 'info'
              if (level === 'ERROR') logType = 'error'
              else if (level === 'WARNING') logType = 'warning'
              else if (level === 'SUCCESS') logType = 'success'

              // 检查是否已经存在相同的日志条目，避免重复添加
              const logExists = logEntries.value.some(entry =>
                entry.time === logTime && entry.message === message.trim()
              )

              if (!logExists) {
                logEntries.value.push({
                  time: logTime,
                  message: message.trim(),
                  type: logType
                })
              }
            } else {
              // 如果解析失败，直接添加原始字符串
              const logExists = logEntries.value.some(entry =>
                entry.message === logString.trim()
              )

              if (!logExists) {
                logEntries.value.push({
                  time: new Date().toLocaleTimeString(),
                  message: logString.trim(),
                  type: 'info'
                })
              }
            }
          })
        }
      } catch (logError) {
        // 如果获取日志失败，不影响主流程
        if (attempts === 1) { // 只在第一次失败时显示警告
          logEntries.value.push({
            time: new Date().toLocaleTimeString(),
            message: '获取任务日志失败',
            type: 'warning'
          })
        }
      }

      // 更新进度条
      if (res.progress !== undefined) {
        const previousProgress = calculationProgress.value
        calculationProgress.value = Math.round(res.progress)

        // 检查进度是否有变化
        if (calculationProgress.value > previousProgress) {
          consecutiveNoProgress = 0
          // 进度有变化，保持较短轮询间隔
          pollInterval = 1000
        } else {
          consecutiveNoProgress++
          // 进度无变化，逐渐增加轮询间隔
          if (consecutiveNoProgress > 3) {
            pollInterval = Math.min(pollInterval * 1.5, 10000) // 最大10秒
          }
        }

        // 如果进度达到100%，标记为完成
        if (res.progress >= 100) {
          calculationStore.completeCalculation()

          // 根据任务类型显示不同的完成消息
          if (taskType === 'placement') {
            toast.success('计算完成', '可以下载结果', 3000)
          } else {
            toast.success('可视化计算完成', '可视化计算已完成', 3000)
          }

          // 如果是可视化计算，需要调用parseSpots接口
          if (taskType !== 'placement') {
            console.log('进度100%完成，进入parseSpots调用分支')
            console.log('taskType:', taskType, '是否进入3D数据获取分支:', taskType !== 'placement')
            try {
              // 调用parseSpots接口解析数据
              console.log('准备调用parseSpotsAPI，taskId:', taskId)

              logEntries.value.push({
                time: new Date().toLocaleTimeString(),
                message: '开始解析可视化计算结果...',
                type: 'info'
              })

              console.log('开始调用parseSpotsAPI...')
              const parseResponse = await retryAPI(() => parseSpotsAPI(taskId), 3, 3000)
              console.log('parseSpots响应:', parseResponse)

              // 更新数据点
              if (parseResponse) {
                console.log('parseSpots返回的数据:', parseResponse)

                // 存储解析后的数据 - 数据直接在根级别
                allViewData.value = {
                  front_spots: parseResponse.front_spots || [],
                  left_spots: parseResponse.left_spots || [],
                  over_spots: parseResponse.over_spots || [],
                  front_peak_spots: parseResponse.front_peak_spots || [],
                  left_peak_spots: parseResponse.left_peak_spots || [],
                  over_peak_spots: parseResponse.over_peak_spots || []
                }

                console.log('更新后的allViewData:', allViewData.value)
                console.log('当前视图:', selectedView.value)

                // 更新当前视图的数据点
                updateDataPointsFromView()

                console.log('更新后的currentDataPoints:', currentDataPoints.value)

                // 更新图表
                updateChart()

                logEntries.value.push({
                  time: new Date().toLocaleTimeString(),
                  message: '可视化数据解析完成，图表已更新',
                  type: 'success'
                })

                toast.success('数据解析完成', '可视化数据已更新到图表', 2000)

                // parseSpotsAPI完成后调用parse3DProbabilityDensity
                try {
                  console.log('parseSpotsAPI完成，开始调用parse3DProbabilityDensity')
                  console.log('调用参数 - taskId:', taskId, 'fileName:', selectedFile.value?.name, 'dataFrames:', dataFrames.value)
                  logEntries.value.push({
                    time: new Date().toLocaleTimeString(),
                    message: '开始解析3D概率密度数据...',
                    type: 'info'
                  })

                  const densityResponse = await retryAPI(() => parse3DProbabilityDensityAPI(taskId, selectedFile.value?.name, dataFrames.value), 3, 5000)
                  console.log('parse3DProbabilityDensity响应:', densityResponse)

                  // 存储3D概率密度数据
                  if (densityResponse) {
                    density3DData.value = densityResponse
                    console.log('3D概率密度数据已存储:', density3DData.value)

                    // 等待下一个tick后更新3D图表
                    nextTick(() => {
                      update3DChart()
                    })
                  }

                  logEntries.value.push({
                    time: new Date().toLocaleTimeString(),
                    message: '3D概率密度数据解析完成',
                    type: 'success'
                  })
                } catch (densityError) {
                  console.error('解析3D概率密度数据失败:', densityError)
                  logEntries.value.push({
                    time: new Date().toLocaleTimeString(),
                    message: `解析3D概率密度数据失败: ${densityError.message || '未知错误'}`,
                    type: 'error'
                  })
                }
              }
            } catch (parseError) {
              console.error('解析可视化数据失败:', parseError)
              let errorMessage = '解析可视化数据失败'

              if (parseError.code === 'ECONNABORTED') {
                errorMessage = '请求超时，3D数据计算需要较长时间，请稍后重试'
              } else if (parseError.message) {
                errorMessage = `解析可视化数据失败: ${parseError.message}`
              }

              logEntries.value.push({
                time: new Date().toLocaleTimeString(),
                message: errorMessage,
                type: 'error'
              })
              toast.error('数据解析失败', errorMessage, 5000)
            }
          }

          return // 停止轮询
        }
      }
      if (res.status === 'completed') {
        // 任务完成
        calculationStore.completeCalculation()

        // 根据任务类型显示不同的完成消息
        let successMessage = '计算完成，可以下载结果'
        let toastMessage = '推算已完成，可以下载结果'

        if (taskType !== 'placement') {
          successMessage = '可视化计算完成'
          toastMessage = '可视化计算已完成'
        }

        // 更新日志
        logEntries.value.push({
          time: new Date().toLocaleTimeString(),
          message: successMessage,
          type: 'success'
        })

        toast.success('计算完成', toastMessage, 3000)

        // 如果是可视化计算完成，调用parseSpots接口解析数据
        console.log('任务完成，taskType:', taskType, 'res.task_type:', res.task_type)
        console.log('判断条件 taskType !== "placement":', taskType !== 'placement')
        if (taskType !== 'placement') {
          console.log('进入parseSpots调用分支')
          console.log('taskType:', taskType, '是否进入3D数据获取分支:', taskType !== 'placement')
          try {
            // 调用parseSpots接口解析数据
            console.log('准备调用parseSpotsAPI，taskId:', taskId)

            logEntries.value.push({
              time: new Date().toLocaleTimeString(),
              message: '开始解析可视化计算结果...',
              type: 'info'
            })

            console.log('开始调用parseSpotsAPI...')
            const parseResponse = await retryAPI(() => parseSpotsAPI(taskId), 3, 3000)
            console.log('parseSpots响应:', parseResponse)

            // 更新数据点
            if (parseResponse) {
              // 存储解析后的数据 - 数据直接在根级别
              allViewData.value = {
                front_spots: parseResponse.front_spots || [],
                left_spots: parseResponse.left_spots || [],
                over_spots: parseResponse.over_spots || [],
                front_peak_spots: parseResponse.front_peak_spots || [],
                left_peak_spots: parseResponse.left_peak_spots || [],
                over_peak_spots: parseResponse.over_peak_spots || []
              }

              // 更新当前视图的数据点
              updateDataPointsFromView()

              // 更新图表
              updateChart()

              logEntries.value.push({
                time: new Date().toLocaleTimeString(),
                message: '可视化数据解析完成，图表已更新',
                type: 'success'
              })

              toast.success('数据解析完成', '可视化数据已更新到图表', 2000)

              // parseSpotsAPI完成后调用parse3DProbabilityDensity
              try {
                console.log('parseSpotsAPI完成，开始调用parse3DProbabilityDensity')
                console.log('调用参数 - taskId:', taskId, 'fileName:', selectedFile.value?.name, 'dataFrames:', dataFrames.value)
                logEntries.value.push({
                  time: new Date().toLocaleTimeString(),
                  message: '开始解析3D概率密度数据...',
                  type: 'info'
                })

                const densityResponse = await retryAPI(() => parse3DProbabilityDensityAPI(taskId, selectedFile.value?.name, dataFrames.value), 3, 5000)
                console.log('parse3DProbabilityDensity响应:', densityResponse)

                // 存储3D概率密度数据
                if (densityResponse) {
                  density3DData.value = densityResponse
                  console.log('3D概率密度数据已存储:', density3DData.value)

                  // 等待下一个tick后更新3D图表
                  nextTick(() => {
                    update3DChart()
                  })
                }

                logEntries.value.push({
                  time: new Date().toLocaleTimeString(),
                  message: '3D概率密度数据解析完成',
                  type: 'success'
                })
              } catch (densityError) {
                console.error('解析3D概率密度数据失败:', densityError)
                logEntries.value.push({
                  time: new Date().toLocaleTimeString(),
                  message: `解析3D概率密度数据失败: ${densityError.message || '未知错误'}`,
                  type: 'error'
                })
              }
            }
          } catch (parseError) {
            console.error('解析可视化数据失败:', parseError)
            let errorMessage = '解析可视化数据失败'

            if (parseError.code === 'ECONNABORTED') {
              errorMessage = '请求超时，3D数据计算需要较长时间，请稍后重试'
            } else if (parseError.message) {
              errorMessage = `解析可视化数据失败: ${parseError.message}`
            }

            logEntries.value.push({
              time: new Date().toLocaleTimeString(),
              message: errorMessage,
              type: 'error'
            })
            toast.error('数据解析失败', errorMessage, 5000)
          }
        } else {
          // 存储计算结果
          if (res.result) {
            // 处理计算结果数据
          }
        }

      } else if (res.status === 'failed') {
        // 任务失败
        throw new Error(res.error || '计算任务失败')

      } else if (res.status === 'pending') {
        // 任务等待中 - 使用较长间隔
        if (attempts < maxAttempts) {
          pollingTimer = setTimeout(poll, 3000) // 3秒后再次检查
        } else {
          throw new Error('计算超时，请稍后重试')
        }
      } else if (res.status === 'running') {
        // 任务运行中
        if (attempts < maxAttempts) {
          // 如果进度有变化，更新日志
          if (res.progress > 0 && res.progress % 10 === 0) {
            logEntries.value.push({
              time: new Date().toLocaleTimeString(),
              message: `计算进行中... ${calculationProgress.value}%`,
              type: 'info'
            })
          }

          // 如果进度未达到100%，继续轮询
          if (res.progress < 100) {
            pollingTimer = setTimeout(poll, pollInterval) // 使用动态间隔
          }
          // 如果进度达到100%，上面的进度检查逻辑会处理完成状态
        } else {
          const timeoutMessage = taskType === 'placement' ? '计算超时，请稍后重试' : '可视化计算超时，请稍后重试'
          throw new Error(timeoutMessage)
        }
      } else {
        // 未知状态 - 使用较长间隔
        if (attempts < maxAttempts) {
          pollingTimer = setTimeout(poll, 5000)
        } else {
          const timeoutMessage = taskType === 'placement' ? '任务状态异常，请稍后重试' : '可视化计算状态异常，请稍后重试'
          throw new Error(timeoutMessage)
        }
      }

    } catch (error) {
      // 详细错误日志
      console.error('pollTaskStatus 错误详情:', {
        error: error,
        message: error.message,
        stack: error.stack,
        taskId: taskId,
        attempts: attempts
      })

      // 重置计算状态
      calculationStore.resetCalculation()

      // 更新日志
      logEntries.value.push({
        time: new Date().toLocaleTimeString(),
        message: `任务状态检查失败: ${error.message}`,
        type: 'error'
      })

      // 如果是数组维度错误，提供更具体的错误信息
      if (error.message && error.message.includes('array dimensions')) {
        toast.error('数据格式错误', '数据点格式不一致，请重新上传文件', 5000)
      } else {
        toast.error('计算失败', error.message || '请检查网络连接', 5000)
      }
    }
  }

  // 开始轮询
  logEntries.value.push({
    time: new Date().toLocaleTimeString(),
    message: `开始监控任务状态，任务ID: ${taskId}`,
    type: 'info'
  })
  poll()
}

// 开始计算
const startCalculate = async () => {
  // 验证输入数据
  const validationErrors = validateInputs()

  if (validationErrors.length > 0) {
    // 显示错误提示
    const errorMessage = validationErrors.join('；')
    toast.error('数据验证失败', errorMessage, 900)
    // 重置计算状态，因为验证失败
    calculationStore.resetCalculation()
    return
  }

  // 重置进度
  calculationProgress.value = 0

  try {
    // 数据格式验证和调试
    console.log('发送给后端的数据验证:')
    console.log('allViewData.value:', allViewData.value)

    // 验证每个视图的数据格式
    Object.keys(allViewData.value).forEach(key => {
      const dataArray = allViewData.value[key] || []
      console.log(`${key} 数据点数量:`, dataArray.length)

      if (dataArray.length > 0) {
        const firstPoint = dataArray[0]
        console.log(`${key} 第一个数据点格式:`, {
          pos: firstPoint.pos,
          posLength: firstPoint.pos ? firstPoint.pos.length : 'undefined',
          color: firstPoint.color,
          colorLength: firstPoint.color ? firstPoint.color.length : 'undefined',
          text: firstPoint.text
        })

        // 检查数据格式一致性
        dataArray.forEach((point, index) => {
          if (!Array.isArray(point.pos) || point.pos.length !== 2) {
            console.error(`${key}[${index}] pos 格式错误:`, point.pos)
          }
          if (!Array.isArray(point.color) || point.color.length !== 3) {
            console.error(`${key}[${index}] color 格式错误:`, {
              color: point.color,
              length: point.color ? point.color.length : 'undefined',
              type: typeof point.color
            })
          }
        })

        // 统计 color 数组长度分布
        const colorLengths = dataArray.map(point => point.color ? point.color.length : 0)
        const lengthCounts = colorLengths.reduce((acc, len) => {
          acc[len] = (acc[len] || 0) + 1
          return acc
        }, {})
        console.log(`${key} color 数组长度分布:`, lengthCounts)
      }
    })

    // 所有验证通过，开始计算
    const res = await startCalculationAPI({
      data: true,
      file_name: fileName.value,
      save: parseInt(retainPoints.value),
      frame: parseInt(dataFrames.value),
      strategy: parseInt(targetPoints.value)
    })
    console.log('计算参数:', res)


    // 开始轮询任务状态
    await pollTaskStatus(res.task_id, 'placement')
  } catch (error) {
    // 重置计算状态
    calculationStore.resetCalculation()

    // 更新日志
    logEntries.value.push({
      time: new Date().toLocaleTimeString(),
      message: `计算失败: ${error.message}`,
      typef: 'error'
    })

    toast.error('计算失败', error.message || '请检查网络连接或参数设置', 5000)
  }
}
// 下载计算结果
const downloadResults = async () => {
  try {
    // 检查计算是否完成
    if (!calculationStore.calculationCompleted) {
      toast.error('下载失败', '请先完成计算任务', 3000)
      return
    }

    // 显示下载开始提示
    toast.info('开始下载', '正在准备下载 .npy 文件...', 2000)

    // 调用API获取计算结果，传入帧数参数
    const res = await downloadResultsAPI(parseInt(dataFrames.value))
    console.log('下载API响应:', res)

    // 生成 .npy 文件名
    const currentTime = new Date().toISOString().replace(/[:.]/g, '-')
    const fileName = `pose-feasibility-results-${currentTime}.npy`

    // 检查响应是否为 Blob 对象（.npy 文件）
    if (res instanceof Blob) {
      // 直接下载 .npy 文件
      const url = URL.createObjectURL(res)
      const link = document.createElement('a')
      link.href = url
      link.download = fileName
      link.style.display = 'none'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(url)

      // 显示下载成功提示
      toast.success('下载成功', `文件 ${fileName} 已开始下载`, 3000)

      // 更新日志
      logEntries.value.push({
        time: new Date().toLocaleTimeString(),
        message: `计算结果已下载: ${fileName}`,
        type: 'success'
      })

      // 下载成功后初始化所有状态
      initializeStates()
      return
    }

    // 如果响应有 data 字段且是 Blob
    if (res && res.data && res.data instanceof Blob) {
      const url = URL.createObjectURL(res.data)
      const link = document.createElement('a')
      link.href = url
      link.download = fileName
      link.style.display = 'none'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(url)

      toast.success('下载成功', `文件 ${fileName} 已开始下载`, 3000)

      // 下载成功后初始化所有状态
      initializeStates()
    }
  } catch (error) {
    console.error('下载失败:', error)
    // 显示错误提示
    toast.error('下载失败', error.message || '下载过程中出现错误', 5000)
  }
}
// 开始优化（别名，用于父组件调用）
const startOptimize = startCalculate

// 暴露方法给父组件
defineExpose({
  startCalculate,
  startOptimize,
  downloadResults,
  handleVisualizeCalculation
})

</script>

<style scoped>
/* 性能优化：启用硬件加速和优化滚动 */
* {
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
}

.pose-feasibility {
  height: 100%;
  display: flex;
  flex-direction: column;
  /* 启用硬件加速 */
  transform: translateZ(0);
  will-change: transform;
}

.progress-section {
  margin-bottom: 20px;
  margin-top: 10px
}

.progress-bar {
  width: 100%;
  height: 12px;
  background: linear-gradient(90deg, #2d2d2d 0%, #3a3a3a 50%, #2d2d2d 100%);
  border-radius: 6px;
  position: relative;
  overflow: hidden;
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.3),
    0 2px 8px rgba(0, 0, 0, 0.2);
  border: 1px solid #444;
  margin-top: 30px
}

.progress-bar::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(90deg,
      transparent 0%,
      rgba(255, 255, 255, 0.1) 50%,
      transparent 100%);
  animation: shimmer 2s infinite;
}

.progress-fill {
  height: 100%;
  background: #485FD4;
  border-radius: 6px;
  transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  overflow: hidden;
  box-shadow:
    0 0 20px rgba(76, 175, 80, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

.progress-fill::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg,
      transparent 0%,
      rgba(255, 255, 255, 0.4) 50%,
      transparent 100%);
  animation: progress-shine 1.5s infinite;
}

.progress-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #ffffff;
  font-weight: 700;
  font-size: 14px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  z-index: 2;
}

@keyframes shimmer {
  0% {
    transform: translateX(-100%);
  }

  100% {
    transform: translateX(100%);
  }
}

@keyframes progress-shine {
  0% {
    left: -100%;
  }

  100% {
    left: 100%;
  }
}

.main-content {
  flex: 1;
  display: flex;
  gap: 20px;

}

.left-panel {
  width: 312px;
  display: flex;
  flex-direction: column;
  /* gap: 20px; */
  justify-content: space-between;
}

.right-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* 视图切换按钮样式 */
.view-switch-container {
  margin-bottom: 20px;
  display: flex;
  justify-content: center;
}

.view-switch-buttons {
  display: flex;
  background: #2d2d2d;
  border-radius: 8px;
  padding: 4px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  border: 1px solid #444;
}

.view-switch-btn {
  padding: 12px 24px;
  background: transparent;
  border: none;
  color: #b0b0b0;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  border-radius: 6px;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
}

.view-switch-btn:hover {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.1);
}

.view-switch-btn.active {
  background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
  color: #ffffff;
  box-shadow: 0 2px 8px rgba(74, 144, 226, 0.4);
}

.view-switch-btn.active::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 50%);
  border-radius: 6px;
}

.control-section {
  background-color: #2a2a2a;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 20px;
}

.section-title {
  font-size: 16px;
  font-weight: 600;
  color: #ffffff;
  margin: 0 0 15px 0;
}

.input-group {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.input-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.input-label {
  min-width: 80px;
  font-size: 14px;
  color: #e0e0e0;
  font-weight: 500;
}

.input-field {
  flex: 1;
  height: 36px;
  padding: 0 12px;
  border: 1px solid #555;
  border-radius: 4px;
  font-size: 14px;
  color: #ffffff;
  background-color: rgba(255, 255, 255, 0.1);
  transition: border-color 0.3s ease;
}

.input-field:focus {
  outline: none;
  border-color: #4a90e2;
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}

.input-field::placeholder {
  color: #999999;
}

.import-btn {
  height: 36px;
  width: 120px;
  /* padding: 0 16px; */
  background: #485FD4;
  color: #ffffff;
  border: none;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.import-btn:hover:not(:disabled) {
  background-color: #357abd;
}

.import-btn:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
  opacity: 0.6;
}

.unit-label {
  font-size: 14px;
  color: #e0e0e0;
  font-weight: 500;
}

.graph-section,
.log-section {
  background: transparent;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  /* padding-bottom: 30px; */
}

.log-section {
  margin-top: 20px;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* margin-bottom: 20px; */
}

.section-actions {
  display: flex;
  gap: 8px;
}

.action-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  background: #485FD4;
  color: #ffffff;
  border: none;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(74, 144, 226, 0.3);
}

.action-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(74, 144, 226, 0.4);
}

.btn-icon {
  font-size: 14px;
}

.graph-container {
  height: calc(100vh - 320px);
  margin-top: 0;
}

/* 双图表布局 */
.charts-layout {
  display: flex;
  gap: 10px;
  /* 缩小间距 */
  height: 100%;
  flex: 1;
  /* 确保占满父容器 */
  min-height: 0;
  /* 确保可以收缩 */
}

.chart-section {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
  border: 1px solid #4a5568;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  overflow: hidden;
  min-height: 0;
  /* 确保可以收缩 */
  position: relative;
  transition: all 0.3s ease;
}

.chart-section::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  z-index: 1;
}

/* 移除网格辅助线 */

.chart-section:hover {
  border-color: #63b3ed;
  box-shadow: 0 6px 20px rgba(99, 179, 237, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.chart-header {
  padding: 15px 20px;
  border-bottom: 1px solid #4a5568;
  background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
  color: #e2e8f0;
}



.view-controls {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

.view-btn {
  padding: 6px 12px;
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 12px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.view-btn:hover {
  background: #f0f0f0;
  border-color: #999;
}

.view-btn.active {
  background: #007bff;
  color: white;
  border-color: #007bff;
}

.chart-2d,
.chart-3d {
  flex: 1;
  min-height: 300px;
  width: 100%;
  height: 100%;
  position: relative;
  z-index: 2;
  /* 性能优化 */
  transform: translateZ(0);
  will-change: transform;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}


.axis {
  position: absolute;
}

.axis.x-axis {
  bottom: 20px;
  left: 20px;
  right: 20px;
  height: 2px;
}

.axis.y-axis {
  left: 20px;
  top: 20px;
  bottom: 20px;
  width: 2px;
}

.axis-line {
  background: linear-gradient(90deg, #6c757d 0%, #495057 50%, #6c757d 100%);
  border-radius: 1px;
}

.x-axis .axis-line {
  width: 100%;
  height: 2px;
}

.y-axis .axis-line {
  width: 2px;
  height: 100%;
}

.axis-label {
  position: absolute;
  font-size: 14px;
  font-weight: 600;
  color: #495057;
  background: #ffffff;
  padding: 4px 8px;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.x-axis .axis-label {
  top: -35px;
  left: 50%;
  transform: translateX(-50%);
}

.y-axis .axis-label {
  left: -50px;
  top: 50%;
  transform: translateY(-50%) rotate(-90deg);
}

.axis-ticks {
  position: absolute;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.x-axis .axis-ticks {
  top: 8px;
  left: 0;
  right: 0;
}

.y-axis .axis-ticks {
  left: 8px;
  top: 0;
  bottom: 0;
  flex-direction: column;
}

.tick {
  font-size: 11px;
  color: #6c757d;
  font-weight: 500;
  background: #ffffff;
  padding: 2px 4px;
  border-radius: 2px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.grid-lines {
  position: absolute;
  top: 30px;
  left: 40px;
  right: 40px;
  bottom: 30px;
}

.grid-line {
  position: absolute;
  background: rgba(108, 117, 125, 0.1);
  border-radius: 0.5px;
}

.grid-line.vertical {
  width: 1px;
  height: 100%;
}

.grid-line.horizontal {
  height: 1px;
  width: 100%;
}

.data-points {
  position: absolute;
  top: 30px;
  left: 40px;
  right: 40px;
  bottom: 30px;
}

.data-point {
  position: absolute;
  width: 6px;
  height: 6px;
  background: radial-gradient(circle, #ff6b6b 0%, #ee5a52 100%);
  border-radius: 50%;
  box-shadow:
    0 2px 4px rgba(255, 107, 107, 0.3),
    0 0 0 2px rgba(255, 107, 107, 0.1);
  animation: dataPointPulse 2s infinite;
}

@keyframes dataPointPulse {

  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.2);
    opacity: 0.8;
  }
}

.legend {
  position: absolute;
  top: 20px;
  right: 20px;
  background: rgba(255, 255, 255, 0.95);
  padding: 12px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(10px);
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  font-size: 12px;
  color: #495057;
  font-weight: 500;
}

.legend-item:last-child {
  margin-bottom: 0;
}

.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(circle, #ff6b6b 0%, #ee5a52 100%);
  box-shadow: 0 1px 3px rgba(255, 107, 107, 0.3);
}

.legend-color.boundary {
  background: linear-gradient(45deg, #4a90e2 0%, #357abd 100%);
  box-shadow: 0 1px 3px rgba(74, 144, 226, 0.3);
}

.log-controls {
  display: flex;
  gap: 8px;
}

.control-btn {
  padding: 6px 12px;
  background: #f8f9fa;
  color: #6c757d;
  border: 1px solid #dee2e6;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
}

.control-btn:hover {
  background: #e9ecef;
  color: #495057;
  border-color: #adb5bd;
}

.log-container {
  height: 200px;
  margin-top: 0;
  flex-shrink: 0;
  padding-right: 30px;
  padding-bottom: 30px;
  margin-bottom: 30px;
}

.log-content {
  width: 100%;
  height: 100%;
  background: #2d2d2d;
  border-radius: 8px;
  padding: 16px;
  overflow-y: auto;
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
}

.log-section-header {
  margin-bottom: 20px;
}

.log-entry {
  display: flex;
  gap: 12px;
  margin-bottom: 8px;
  font-size: 13px;
  line-height: 1.4;
}

.log-entry:last-child {
  margin-bottom: 0;
}

.log-time {
  color: #6c757d;
  font-weight: 500;
  min-width: 70px;
  flex-shrink: 0;
}

.log-message {
  color: #ffffff;
  flex: 1;
}

.log-entry.info .log-message {
  color: #17a2b8;
}

.log-entry.success .log-message {
  color: #28a745;
}

.log-entry.warning .log-message {
  color: #ffc107;
}

.log-entry.error .log-message {
  color: #dc3545;
}

/* 滚动条样式 */
.log-content::-webkit-scrollbar {
  width: 6px;
}

.log-content::-webkit-scrollbar-track {
  background: #2d2d2d;
  border-radius: 3px;
}

.log-content::-webkit-scrollbar-thumb {
  background: #495057;
  border-radius: 3px;
}

.log-content::-webkit-scrollbar-thumb:hover {
  background: #6c757d;
}
</style>